//=====================================================================  	
//
//  File:       IncrementTask.cs
//
//  Summary:    This file contains a Control flow task to increment 
//              an integer variable.
//
//  Date:       06/09/2005
//
//---------------------------------------------------------------------
//  This file is part of the Microsoft SQL Server Code Samples.
//  Copyright (C) Microsoft Corporation.  All rights reserved.
//
//  This source code is intended only as a supplement to Microsoft
//  Development Tools and/or on-line documentation.  See these other
//  materials for detailed information regarding Microsoft code samples.
//
//  THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF 
//  ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO 
//  THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
//  PARTICULAR PURPOSE.
//=====================================================================                         

using System;
using System.Xml;
using System.Threading;
using System.Runtime.InteropServices;
using Microsoft.SqlServer.Dts.Runtime;

namespace Microsoft.Samples.SqlServer.Dts
{
    [
    System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Interoperability", "CA1405:ComVisibleTypeBaseTypesShouldBeComVisible"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1001:TypesThatOwnDisposableFieldsShouldBeDisposable"), Guid("9B916A96-98F4-4953-B247-A7C717A89DE2"),
    DtsTask
        (
        DisplayName = "IncrementTaskCS",
        Description = "IncrementTask Sample",
        UITypeName = "Microsoft.Samples.SqlServer.Dts.IncrementTaskUI,IncrementTaskCS,Version=1.0.0.0,Culture=Neutral,PublicKeyToken=b23be0feabed1355")
    ]
    public sealed class IncrementTask : Task, IDTSComponentPersist, IDTSBreakpointSite
    {

        private int initialValueInternal;
        private int loopCountValue;
        private int incrementValueInternal;
        private int postExecutionValue;
        private int suspendRequiredValue;
        private int debugModeValue;
        private BreakpointManager bpm;
        private EventInfos eventInfosValue;
        private EventInfo onBeforeIncrement;
        private EventInfo onAfterIncrement;

        private ManualResetEvent suspended;
        private ManualResetEvent canExecute;

        // Provided when firing events.
        // When return variable is set to false event will not be fired again.
        bool bFireOnAfterIncrement = true;
        bool bFireOnBeforeIncrement = true;
        bool bFireError = true;
        bool bFireWarning = true;

        #region Ctor

        public IncrementTask()
        {
            this.suspended = new ManualResetEvent(true);
            this.canExecute = new ManualResetEvent(true);
        }

        #endregion

        #region Properties

        public int LoopCount
        {
            get
            {
                return this.loopCountValue;
            }
            set
            {
                this.loopCountValue = value;
            }
        }
        public int InitialValue
        {
            get
            {
                return this.initialValueInternal;
            }
            set
            {
                this.initialValueInternal = value;
            }
        }
        public int IncrementValue
        {
            get
            {
                return this.incrementValueInternal;
            }
            set
            {
                this.incrementValueInternal = value;
            }
        }
        #endregion

        #region Task Base Class overrides
        /// <summary>
        /// Called by the DTS Runtime Engine after creating the task. The EventInfos object is used 
        /// to declare the custom OnBeforeIncrement and OnAfterIncrement events this task raises during exeuction. 
        /// </summary>
        /// <param name="connections">The package's Connections collection.</param>
        /// <param name="variableDispenser">The System variables visible to the task.</param>
        /// <param name="events">Used to raise events during execution.</param>
        /// <param name="log">Used to write log entries.</param>
        /// <param name="eventInfos">The EventInfos object</param>
        public override void InitializeTask(Connections connections, VariableDispenser variableDispenser, IDTSInfoEvents events, IDTSLogging log, EventInfos eventInfos, LogEntryInfos logEntryInfos, ObjectReferenceTracker refTracker)
        {
            if (eventInfos == null)
            {
                throw new ArgumentNullException("eventInfos");
            }

            this.eventInfosValue = eventInfos;
            string[] paramNames = new string[1];
            TypeCode[] paramTypes = new TypeCode[] { TypeCode.Int32 };
            string[] paramDescriptions = new string[1];

            paramNames[0] = "InitialValue";
            paramDescriptions[0] = "The value before increment.";

            this.eventInfosValue.Add("OnBeforeIncrement", "Fires before the task increments the value.", true, paramNames, paramTypes, paramDescriptions);
            this.onBeforeIncrement = this.eventInfosValue["OnBeforeIncrement"];

            paramDescriptions[0] = "The value after increment.";
            this.eventInfosValue.Add("OnAfterIncrement", "Fires after the initial value is updated.", true, paramNames, paramTypes, paramDescriptions);
            this.onAfterIncrement = this.eventInfosValue["OnAfterIncrement"];
        }
        /// <summary>
        /// Called by the TaskHost.
        /// </summary>
        /// <param name="connections">The package's Connections collection.</param>
        /// <param name="variableDispenser">The System variables visible to the task.</param>
        /// <param name="componentEvents">Used to raise events during execution.</param>
        /// <param name="log">Used to write log entries.</param>
        /// <param name="transaction">Used to enlist in transactions.</param>
        /// <returns>Returns a value from the DTSExecResult enumeration indicating execution success or failure.</returns>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2201:DoNotRaiseReservedExceptionTypes"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1303:DoNotPassLiteralsAsLocalizedParameters", MessageId = "System.Exception.#ctor(System.String)")]
        public override DTSExecResult Execute(Connections connections, VariableDispenser variableDispenser, IDTSComponentEvents componentEvents, IDTSLogging log, object transaction)
        {
            if (componentEvents == null)
            {
                throw new ArgumentNullException("componentEvents");
            }

            // While a task is not executing it is suspended.  Now that we are executing,
            // change to not suspended.
            ChangeEvent(suspended, false);

            // Check for a suspend before doing any work in case the suspend and execute calls
            // were initiated at virtually the same time.
            CheckAndSuspend();

            if (componentEvents.FireQueryCancel() == true)
            {
                return DTSExecResult.Canceled;
            }

            this.postExecutionValue = this.initialValueInternal;

            if (this.loopCountValue == 0)
            {
                throw new Exception("LoopCount is zero.");
            }

            object[] arguments = new object[1];

            for (int x = 0; x < this.loopCountValue; x++)
            {
                this.CheckAndFireBreakpoint(componentEvents, 1);
                //	Fire the OnBeforeIncrement event
                arguments[0] = this.postExecutionValue;
                if (bFireOnBeforeIncrement)
                {
                    componentEvents.FireCustomEvent(this.onBeforeIncrement.Name,
                        this.onBeforeIncrement.Description, ref arguments,
                        null, ref bFireOnBeforeIncrement);
                }

                this.CheckAndFireBreakpoint(componentEvents, 2);
                // increment/decrement the initial value
                this.postExecutionValue += this.incrementValueInternal;

                //	Fire the onAfter increment event
                arguments[0] = this.postExecutionValue;
                if (bFireOnAfterIncrement)
                {
                    componentEvents.FireCustomEvent(this.onAfterIncrement.Name,
                        this.onAfterIncrement.Description, ref arguments,
                        null, ref bFireOnAfterIncrement);
                }
            }

            ChangeEvent(suspended, true);
            return DTSExecResult.Success;
        }

        /// <summary>
        /// Called by the TaskHost object. Verifies that the LoopCount, InitialValue, and IncrementValues are set correctly.
        /// </summary>
        /// <param name="connections">The package's Connections collection.</param>
        /// <param name="variableDispenser">The System variables visible to the task.</param>
        /// <param name="componentEvents">Used to raise events during execution.</param>
        /// <param name="log">Used to write log entries.</param>
        /// <returns>Returns a value from the DTSExecResult enumeration indicating validation success or failure.</returns>
        public override DTSExecResult Validate(Connections connections, VariableDispenser variableDispenser, IDTSComponentEvents componentEvents, IDTSLogging log)
        {
            if (componentEvents == null)
            {
                throw new ArgumentNullException("componentEvents");
            }

            if (this.loopCountValue == 0)
            {
                if (bFireError)
                {
                    componentEvents.FireError(0, "IncrementTask", "LoopCount cannot be zero.", "", 0);
                }

                return DTSExecResult.Failure;
            }
            else if (this.incrementValueInternal == 0)
            {
                if (bFireWarning)
                {
                    componentEvents.FireWarning(0, "IncrementTask", "The IncrementValue property is zero.", "", 0);
                }
            }

            return DTSExecResult.Success;
        }

        /// <summary>
        /// Returns the result of the Execution of the task
        /// property.
        /// </summary>
        public override object ExecutionValue
        {
            get
            {
                return this.postExecutionValue;
            }
        }
        #endregion

        #region IDTSComponentPersist Members
        /// <summary>
        /// Store the custom properties in the package xml.
        /// </summary>
        /// <param name="doc">XmlDocument provided by the DTS Runtime Engine</param>
        /// <param name="infoEvents">IDTSInfoEvents interface</param>
        void IDTSComponentPersist.SaveToXML(System.Xml.XmlDocument doc, IDTSInfoEvents infoEvents)
        {
            XmlElement taskElement = doc.CreateElement(string.Empty,
                "IncrementTask", string.Empty);

            //	LoopCount property
            XmlAttribute loopCountAttribute = doc.CreateAttribute(string.Empty,
                "LoopCount", string.Empty);

            loopCountAttribute.Value
                = this.loopCountValue.ToString(
                System.Globalization.CultureInfo.InvariantCulture);

            //	InitialValue property
            XmlAttribute initialValueAttribute = doc.CreateAttribute(string.Empty,
                "InitialValue", string.Empty);
            initialValueAttribute.Value
                = this.initialValueInternal.ToString(
                System.Globalization.CultureInfo.InvariantCulture);

            //	IncrementValue property
            XmlAttribute incrementValueAttribute = doc.CreateAttribute(string.Empty,
                "IncrementValue", string.Empty);
            incrementValueAttribute.Value
                = this.incrementValueInternal.ToString(
                System.Globalization.CultureInfo.InvariantCulture);

            taskElement.Attributes.Append(loopCountAttribute);
            taskElement.Attributes.Append(initialValueAttribute);
            taskElement.Attributes.Append(incrementValueAttribute);

            doc.AppendChild(taskElement);
        }

        /// <summary>
        /// Initializes the task from the data stored in the package's XmlDocument.
        /// </summary>
        /// <param name="node">The Task's XmlNode</param>
        /// <param name="infoEvents">IDTSComponentEvents Interface</param>
        void IDTSComponentPersist.LoadFromXML(System.Xml.XmlElement node, IDTSInfoEvents infoEvents)
        {
            //	This might occur if the task's XML has been modified outside of the Business Intelligence
            //	Or SQL Server Workbenches.
            if (node.Name != "IncrementTask")
            {
                throw new Exception("Unexpected task element when loading task.");
            }
            else
            {
                try
                {
                    this.loopCountValue
                        = System.Convert.ToInt32(node.Attributes.GetNamedItem("LoopCount").Value,
                        System.Globalization.CultureInfo.InvariantCulture);
                    this.initialValueInternal
                        = System.Convert.ToInt32(node.Attributes.GetNamedItem("InitialValue").Value,
                        System.Globalization.CultureInfo.InvariantCulture);
                    this.incrementValueInternal
                        = System.Convert.ToInt32(node.Attributes.GetNamedItem("IncrementValue").Value,
                        System.Globalization.CultureInfo.InvariantCulture);
                }
                catch
                {
                    throw;
                }
            }
        }

        #endregion

        #region IDTSBreakpointSite Members

        void IDTSBreakpointSite.AcceptBreakpointManager(BreakpointManager breakpointManager)
        {
            this.bpm = breakpointManager;
            this.bpm.CreateBreakpointTarget(1, "Break when the container receives the OnBeforeIncrement event.");
            this.bpm.CreateBreakpointTarget(2, "Break when the container receives the OnAfterIncrement event.");
        }

        bool IDTSBreakpointSite.DebugMode
        {
            get
            {
                return this.debugModeValue != 0;
            }
            set
            {
                Interlocked.Exchange(ref this.debugModeValue, value ? 1 : 0);
            }
        }
        #endregion

        #region IDTSSuspend Members

        bool IDTSSuspend.SuspendRequired
        {
            get
            {
                return this.suspendRequiredValue != 0;
            }
            set
            {
                // This lock is also taken by Suspend().  Since it is possible for the package to be
                // suspended and resumed in quick succession, this property put might happen before
                // the actual Suspend() call.  Without the lock, the Suspend() might reset the canExecute
                // event after we set it to abort the suspension.
                lock (this)
                {
                    Interlocked.Exchange(ref this.suspendRequiredValue, value ? 1 : 0);

                    if (!value)
                    {
                        ChangeEvent(canExecute, true);
                    }
                }
            }

        }

        void IDTSSuspend.ResumeExecution()
        {
            ChangeEvent(canExecute, true);
        }

        void IDTSSuspend.SuspendExecution()
        {
            // This lock is also taken by the set SuspendRequired method.  It prevents this
            // call from override an aborted suspension.  See comments in set SuspendRequired.
            lock (this)
            {
                // If a suspend is required, do it.
                if (this.suspendRequiredValue != 0)
                {
                    ChangeEvent(canExecute, false);
                }
            }

            // We can't return from Suspend until the task is "suspended".  This can happen
            // one of two way: the suspended event occurs, indicating that the execute thread
            // has suspended or the canExecute flag is set, indicating that a suspend is no
            // longer required.
            WaitHandle[] suspendOperationComplete = { suspended, canExecute };
            WaitHandle.WaitAny(suspendOperationComplete);
        }

        #endregion

        #region Breakpoint Helper Functions
        private void CheckAndSuspend()
        {
            // Loop until we can execute.  The loop is required rather than a simple if
            // because there is a time between the return from WaitOne and the reset that we
            // might receive another Suspend call.  Suspend() will see that we are suspended
            // and return.  So we need to rewait.
            while (!canExecute.WaitOne(0, false))
            {
                ChangeEvent(suspended, true);
                canExecute.WaitOne();
                ChangeEvent(suspended, false);
            }
        }

        private void CheckAndFireBreakpoint(IDTSComponentEvents events, int breakpointID)
        {
            // If the breakpoint is enabled, fire it.
            if (this.debugModeValue != 0 && this.bpm.IsBreakpointTargetEnabled(breakpointID))
            {
                // Enter a suspend mode before firing the breakpoint.  Firing the breakpoint
                // will cause the runtime to call Suspend on this task.  Since we are blocked
                // on the breakpoint, we are suspended, so indicate that.
                ChangeEvent(suspended, true);
                events.FireBreakpointHit(this.bpm.GetBreakpointTarget(breakpointID));
                ChangeEvent(suspended, false);
            }

            // Check for a suspension for two reasons: 1. If we are at a point where we could
            // fire a breakpoint, we are at a valid suspend point.  Even if we didn't hit a
            // breakpoint, the runtime may have called suspend, so check for it.  2. Between
            // the return from OnBreakpointHit and the reset of the event, it is possible to have
            // received a suspend call that we returned from because we were suspended.  We need
            // to be sure it is ok to continue executing now.
            CheckAndSuspend();
        }

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2201:DoNotRaiseReservedExceptionTypes"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1303:DoNotPassLiteralsAsLocalizedParameters", MessageId = "System.Exception.#ctor(System.String)")]
        static void ChangeEvent(ManualResetEvent e, bool shouldSet)
        {
            bool succeeded;

            if (shouldSet)
            {
                succeeded = e.Set();
            }
            else
            {
                succeeded = e.Reset();
            }

            if (!succeeded)
            {
                throw new Exception("Synchronization object failed.");
            }
        }
        #endregion
    }
}
